import numpy as np
import matplotlib.pyplot as plt
import IPython
import warnings
warnings.filterwarnings("ignore")
%matplotlib inline
import librosa
import librosa.display
#Choose the song to execute and the duration (in sec)
song = 'smellsliketeenspirits.mp3'
Il metodo load carica un file audio come una serie temporale a floating point (y) che verrà ricampionato automaticamente alla frequenza indicata (sr, default=22050)
duration = 60
y, sr = librosa.load(song, duration=duration)
#Plotto la traccia audio e la riproduco
librosa.display.waveplot(y)
IPython.display.Audio(data=y, rate=sr)
La "stft" è la short time Fourier transform: consiste nella trasformata di Fourier ma divide un segnale a lunga durata e lo divide in segmenti della stessa lunghezza, dopodiché ne esegue per ognuno la trasformata di Fourier.
y_ft = librosa.stft(y)
Siccome il range di pressione sonora percepibile è molto più ampio rispetto alla percezione umana approssimiamo la scala di valori utilizzando "amplitude_to_db" che converte il segnale in una scala logaritmica. Il risultato quindi è un plot in cui riusciamo a vedere corrispondenze con quello che sentiamo.
Lo spettrogramma rappresenta un segnale che mostra la relazione tra 3 variabili che caratterizzano qualsiasi suono:
Frequenza (asse verticale);
Tempo (asse orizzontale);
Intensità (scala di colori).
Attraverso l’uso di uno spettrogramma è possibile rendersi conto di come è costituito un suono.
plt.figure()
dbScaled = librosa.amplitude_to_db(np.abs(y_ft), ref=np.max)
librosa.display.specshow(dbScaled, y_axis='log')
plt.colorbar(format= '%+2.0f dB')
plt.title('Full track spectrogram')
plt.tight_layout()
Per trovare le due maschere andremo ad utilizzare il metodo che utilizza la funzione chiamata in precedenza per trovare le due parti in modo rapido: librosa.decompose.hpss. Siccome il metodo in precedenza chiamava in pipeline tre metodi esterni noi andremo a passare alla decompose il risultato della stft. Il parametro "margin" permette di isolare al meglio le singole parti, se il valore è una tupla allora andiamo ad esaltare con valori diversi le due parti (harm,perc)
mask_harm, mask_perc = librosa.decompose.hpss(y_ft, mask=True, margin=(5.0, 3.0))
plt.figure(figsize=(10,4))
plt.subplot(2, 2, 1)
librosa.display.waveplot(mask_harm)
plt.title('Harmonic mask waveplot')
plt.subplot(2, 2, 2)
librosa.display.waveplot(mask_perc)
plt.title('Percussive mask waveplot')
plt.tight_layout()
Rendiamo positivi tutti i valori del segnale dopo la trasformata di Fourier e troviamo la potenza giusta, che quindi isoli bene le due parti limitando il più possibili errori o "buchi", ricavando il segnale intero.
Infine calcoliamo la fase che non è una proprietà di un solo segnale ma implica invece la relazione tra due o più segnali che condividono la stessa frequenza. Se i picchi di due segnali con la stessa frequenza sono esattamente allineati allo stesso tempo si dice che sono in fase.
power=1.5
S_full = np.abs(y_ft)
S_full **= power
phase = np.exp(1.0j * np.angle(y_ft))
Adesso andiamo ad applicare la maschera al segnale iniziale e moltiplichiamo anche per la phase. Dopo questi calcoli ci basta effettuare la trasformata di Fourier inversa per ottenere i due segnali divisi.
harm_masked = mask_harm * S_full * phase
perc_masked = mask_perc * S_full * phase
y_harmonic = librosa.istft(harm_masked)
y_percussive = librosa.istft(perc_masked)
librosa.display.waveplot(y_harmonic)
IPython.display.Audio(data=y_harmonic, rate=sr)
librosa.display.waveplot(y_percussive)
IPython.display.Audio(data=y_percussive, rate=sr)
Questa volta non andiamo a creare delle maschere da applicare poi al segnale ma andremo a ritornare gli spettrogrammi relativi a parte armonica e percussiva. Assegnando al "margin" un valore maggiore di 1.0 andremo anche a ricavare il residuo, quindi i valori che non appartengono a nessuna delle due parti, utilizziamo il valore impostato al momento della creazione delle maschere.
Harm, Perc= librosa.decompose.hpss(y_ft, margin=(5.0, 3.0))
Res = y_ft - (Harm + Perc)
Il primo plot è lo spettrogramma relativo alla canzone iniziale senza modifiche, gli altri due sono gli spettrogrammi della canzone filtrata (senza rumore) con la maschera relativa applicata
plt.figure(figsize=(12,4))
plt.subplot(1, 3, 1)
librosa.display.specshow(dbScaled, y_axis='log')
plt.title('Full track spectrogram')
plt.subplot(1, 3, 2)
librosa.display.specshow(librosa.amplitude_to_db(np.abs(Harm),ref=np.max), y_axis='log')
plt.title('Harmonic spectrogram')
plt.subplot(1, 3, 3)
librosa.display.specshow(librosa.amplitude_to_db(np.abs(Perc), ref=np.max), y_axis='log')
plt.title('Percussive spectrogram')
plt.colorbar(format='%+2.0f dB')
plt.tight_layout()
Come si può notare i valori del residuo sono alti, questo perché essendo una canzone complessa, con molti strumenti, è difficile isolare solo la parte armonica e percussiva
plt.figure()
resSpec = librosa.amplitude_to_db(np.abs(Res), ref=np.max)
librosa.display.specshow(resSpec, y_axis='log')
plt.colorbar(format= '%+2.0f dB')
plt.title('Residual parts spectrogram')
plt.tight_layout()
Utilizziamo la nn_filter per pulire il segnale, la metrica è del coseno quindi applichiamo la similarità del coseno e separiamo i frame simili di almeno 2 secondi in modo tale da non essere ingannati dalla continuità locale.
In seguito andiamo a prendere i valori minimi di entrambi i segnali
S_filter = librosa.decompose.nn_filter(S_full,
aggregate=np.median,
metric='cosine',
width=int(librosa.time_to_frames(2, sr=sr)))
S_filter= np.minimum(S_full, S_filter)
Per creare la maschera questa volta utilizziamo il metodo softmask, utilizzato internamente anche da decompose.hpss. Come parametro per la maschera abbiamo bisogno di una potenza e di un margine, utilizzato per accentuare valori bassi e quindi isolare solo la parte strumentale
margin_i = 2
power = 2
mask_i = librosa.util.softmask(S_filter, margin_i * (S_full - S_filter), power=power)
Applichiamo la maschera al segnale filtrato e ne effettuiamo la trasformata di Fourier inversa per ottenere la traccia audio filtrata della parte vocale
S_background = mask_i * S_filter * phase
back_y = librosa.istft(S_background)
librosa.display.waveplot(back_y)
IPython.display.Audio(data=back_y, rate=sr)
Per non ripetere le stesse operazioni questa volta utilizzeremo un metodo della libreria librosa che esegue in pipeline le operazioni effettuate in precedenza, quindi:
stft -> decompose.hpss -> istft
Otteniamo quindi le due parti già divise
back_harmonic, back_percussive = librosa.effects.hpss(back_y)
librosa.display.waveplot(back_harmonic)
IPython.display.Audio(data=back_harmonic, rate=sr)
librosa.display.waveplot(back_percussive)
IPython.display.Audio(data=back_percussive, rate=sr)
song = 'thatslife.mp3'
duration = 60
y_extra, sr_extra = librosa.load(song, duration=duration)
y_extra_ft = librosa.stft(y_extra)
power=1.5
S_full = np.abs(y_extra_ft)
S_full **= power
phase = np.exp(1.0j * np.angle(y_extra_ft))
S_filter = librosa.decompose.nn_filter(S_full,
aggregate=np.median,
metric='cosine',
width=int(librosa.time_to_frames(2, sr=sr_extra)))
S_filter= np.minimum(S_full, S_filter)
margin_i = 3
power = 2.5
mask_i_extra = librosa.util.softmask(S_filter, margin_i * (S_full - S_filter), power=power)
S_background = mask_i_extra * S_filter * phase
back_y_extra = librosa.istft(S_background)
librosa.display.waveplot(back_y_extra)
IPython.display.Audio(data=back_y_extra, rate=sr)
Questa volta non creiamo delle maschere ma andiamo direttamente a ritornare i due spettrogrammi con lo stesso margine.
harm_extra_old, perc_extra_old = librosa.decompose.hpss(S_background, margin=(5.0, 3.0))
y_extra_harm_old = librosa.istft(harm_extra_old)
y_extra_perc_old = librosa.istft(perc_extra_old)
librosa.display.waveplot(y_extra_harm_old)
IPython.display.Audio(data=y_extra_harm_old, rate=sr_extra)
librosa.display.waveplot(y_extra_perc_old)
IPython.display.Audio(data=y_extra_perc_old, rate=sr_extra)
harm_extra, perc_extra = librosa.decompose.hpss(S_background, margin=(3.5, 4.5))
y_extra_harm = librosa.istft(harm_extra)
y_extra_perc = librosa.istft(perc_extra)
librosa.display.waveplot(y_extra_harm)
IPython.display.Audio(data=y_extra_harm, rate=sr_extra)
librosa.display.waveplot(y_extra_perc)
IPython.display.Audio(data=y_extra_perc, rate=sr_extra)